源码分析 mybatis 读取配置文件

持久层框架封装了jdbc的底层代码,我们需要提供的即sql、映射规则、pojo
研究mybatis,即

  1. mybatis怎么读取配置文件创建SqlSessionFactory
  2. SqlSession运行过程

这里更多探究的是mybatis怎么读取配置文件创建SqlSessionFactory

[TOC]

一.简单执行流程

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {//执行如下代码
String resource= "mybatis.cfg.xml";
InputStream in = null;
try {
in = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.close();
}

第一步,mybatis运行,创建SqlSessionFactoryBuilder工厂对象,调用build方法,读取配置文件信息存储在Configuration对象中,并创建SqlSessionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());//通过XML解析器解析,获得DefaultSqlSession对象
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}

第二步.创建SqlSession,赋予属性,从上一步获得的Configuration对象中,得到Environment环境信息,创建事务工厂,根据传入的事务隔离级别、是否自动提交的参数,创建事务对象。再根据事务、配置对象的执行器类型创建执行器对象,通过执行器对象创建SqlSession
即事务工厂–>事务–>执行器–>SqlSession

1
2
3
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;

DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();//读取配置文件中的环境信息
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);//创建事务工厂
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//根据传入的TransactionIsolationLevel、autoCommit创建事务对象
Executor executor = this.configuration.newExecutor(tx, execType);//根据事务、配置对象的执行器类型创建执行器对象,这里是SimpleExecutor
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}

return var8;
}
  • dirty:true sql语句执行完毕后,事务提交,false sql语句执行错误,事务回滚
  • Executor;可用于创建Statement对象,依靠MapperStatement对象将赋值内容与占位符进行绑定

第三步.SqlSession关闭

二.疑惑

mybatis有xml、注解的使用方式
mybatis的使用方式(如果对使用方式不熟可以看这篇)

1.对于Sql的xml是怎么被读取的、为什么xml文件名要规定一致,sql的#{ }是怎么替换的?
2.对于注解又是怎么读取注解中的配置的?

三.解决

1.第一个问题

  1. 创建SqlSessionFactory对象,关键在于这个build方法读取配置文件,并将配置信息放到Configuration对象中,进入build方法
    1
    2
    3
    4
    5
    6
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    var5 = this.build(parser.parse());//通过XML解析器解析,获得DefaultSqlSessionFactory对象
    } //...

发现其中创建了一个XML解析器,调用了parse方法,进入parse方法

  1. parse代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public Configuration parse() {
    if (this.parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
    this.parsed = true;
    this.parseConfiguration(this.parser.evalNode("/configuration"));
    return this.configuration;
    }
    }

可以看到parse方法调用parseConfiguration找到configuration标签,读取内部内容,具体又是怎么读呢? 进入parseConfiguration方法

  1. parseConfiguration方法如下,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private void parseConfiguration(XNode root) {
    try {
    this.propertiesElement(root.evalNode("properties"));
    Properties settings = this.settingsAsProperties(root.evalNode("settings"));
    this.loadCustomVfs(settings);
    this.typeAliasesElement(root.evalNode("typeAliases"));
    this.pluginElement(root.evalNode("plugins"));
    this.objectFactoryElement(root.evalNode("objectFactory"));
    this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
    this.settingsElement(settings);
    this.environmentsElement(root.evalNode("environments"));
    this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    this.typeHandlerElement(root.evalNode("typeHandlers"));
    this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
    }

可以看到很多见过的属性,即读取主配置文件的属性进到Configuration对象中,具体怎么读,以读取mappers为例(其下有package、mapper标签)进入mapperElement方法

  1. mapperElement方法如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
    Iterator var2 = parent.getChildren().iterator();
    while(true) {
    while(var2.hasNext()) {
    XNode child = (XNode)var2.next();
    String resource;
    if ("package".equals(child.getName())) {
    resource = child.getStringAttribute("name");
    this.configuration.addMappers(resource);
    } else {
    resource = child.getStringAttribute("resource");
    String url = child.getStringAttribute("url");
    String mapperClass = child.getStringAttribute("class");
    XMLMapperBuilder mapperParser;
    InputStream inputStream;
    if (resource != null && url == null && mapperClass == null) {
    //...没截全
    }

该方法判断是package还是mapper标签,然后读取进Configuration对象,具体怎么读呢?以packeage为例

  1. 读取package的代码如下
    1
    2
    3
    4
    if ("package".equals(child.getName())) {
    resource = child.getStringAttribute("name");
    this.configuration.addMappers(resource);
    }

resource获得包名后,调用addMappers,有多个重载,最终调用到参数类型为String packageName, Class<?> superType的addMappers

1
2
3
public void addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}

1
2
3
public void addMappers(String packageName) {
this.addMappers(packageName, Object.class);
}
  1. 最终调用的addMappers方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void addMappers(String packageName, Class<?> superType) {//最终调用到这个addMapper方法
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
    resolverUtil.find(new IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    Iterator var5 = mapperSet.iterator();

    while(var5.hasNext()) {
    Class<?> mapperClass = (Class)var5.next();
    this.addMapper(mapperClass);
    }
    }

其中find方法去找当前工程有无该包,若有,经过匹配后,会加入到该resolverUtil中的Set对象中,通过getClasses得到该添加完毕的set对象。调用自身参数类型为Class type的addMapper方法,进入该addMapper方法

  1. 自身参数类型为Class type的addMapper方法如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
    if (this.hasMapper(type)) {
    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
    this.knownMappers.put(type, new MapperProxyFactory(type));//准备加载的Class对象的map
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
    parser.parse();
    loadCompleted = true;//加载完成,继续放在该map中
    } finally {
    if (!loadCompleted) {//加载未完成,从该map中移除出去
    this.knownMappers.remove(type);
    }
    }
    }
    }

可以看到,最终通过MapperAnnotationBuilder.parser完成解析,从名称上Mapper注解的构建解析,解析过程肯定在这里,进去看看

  1. 具体的MapperAnnotationBuilder.parser方法,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public void parse() {
    String resource = this.type.toString();
    if (!this.configuration.isResourceLoaded(resource)) {//已加载过则不执行
    this.loadXmlResource();//加载寻找package对应的xml文件,有的话,则加载。没有则执行完向下
    this.configuration.addLoadedResource(resource);//里面是Set,表示已加载的resource
    this.assistant.setCurrentNamespace(this.type.getName());
    this.parseCache();//解析缓存
    this.parseCacheRef();
    Method[] methods = this.type.getMethods();
    Method[] var3 = methods;
    int var4 = methods.length;

    for(int var5 = 0; var5 < var4; ++var5) {
    Method method = var3[var5];

    try {
    if (!method.isBridge()) {
    this.parseStatement(method);//如果读取了xml文件不会进入,未读,则在该方法中去类中读取注解
    }
    } catch (IncompleteElementException var8) {
    this.configuration.addIncompleteMethod(new MethodResolver(this, method));
    }
    }
    }
    this.parsePendingMethods();
    }

其中有loadXmlResource方法,装载xml文件,装载什么配置文件呢,主配置文件装载过了,显然是映射的配置文件,进去看看

  1. loadXmlResource代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    private void loadXmlResource() {
    if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
    String xmlResource = this.type.getName().replace('.', '/') + ".xml";//将配置文件中package配的/换成. ,得到对应的xml的文件名称(这就是为什么文件名称要统一的原因)
    InputStream inputStream = null;
    try {
    inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
    } catch (IOException var4) {
    ;
    }
    if (inputStream != null) {//读取到对应的配置文件
    XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
    xmlParser.parse();//进行解析
    }
    }
    }

我们可以看到在这里会将配置文件中package配的”.”换成”/ “,得到对应的xml的文件名称(这就是为什么文件名称要统一的原因),读取了该文件调用了xmlParser.parse()方法

  1. xmlParser.parse()方法具体解析过程如下
    *1.parse进行具体解析,代码如下,可以看到有个configurationElement方法读取了xml里mapper的sql信息,怎么读取的呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
    this.configurationElement(this.parser.evalNode("/mapper"));//读取到mapper标签的内容进行具体解析,这个方法将参数换成了占位符!!!重要
    this.configuration.addLoadedResource(this.resource);//加入到已读map中
    this.bindMapperForNamespace();
    }
    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
    }

    *2.进入configurationElement方法,代码如下,其中的buildStatementFromContext方法中读取了具体的sql操作的标签信息,进buildStatementFromContext看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void configurationElement(XNode context) {
try {//获取对应参数
String namespace = context.getStringAttribute("namespace");//获取命名空间
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));//获取缓存信息
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));/
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));//获取动态sql标签
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//具体的读取sql!!!
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
*3.追踪进buildStatementFromContext方法中,代码如下    
1
2
3
4
5
6
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {//获取数据库的配置信息,不为null,则从Configuration中读取
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}//null时,传入null
this.buildStatementFromContext(list, (String)null);
}
*4.继续进入buildStatementFromContext方法中,名字上可以看出是创建Statement对象从Context中(应该是指写在xml中的sql语句),在该方法中有一个statementParser.parseStatementNode()方法值得注意

(看到了熟悉的Statement)

1
2
3
4
5
6
7
8
9
10
11
12
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {//获得单个的XNode
XNode context = (XNode)var3.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();//进行解析!!!
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}

*5.进入statementParser.parseStatementNode方法,很多熟悉的配方,答案不远了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultSetType = this.context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);


SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);//SqlSource(sql语句来源),这里调用了一个createSqlSource方法


String resultSets = this.context.getStringAttribute("resultSets");
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}

发现有个方法createSqlSource,看名字就是创建sqlSource

当parseStatementNode方法执行完毕时,会将结果放到MappedStatement里,即配置文件中所有的mapper信息读取完毕

MappedStatement中存放的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
*6. 通过debug,进入到一个名为XMLLanguageDriver的类中的createSqlSource
1
2
3
4
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
*7.打开builder.parseScriptNode()方法,可以看到在这里开始判断使用哪种解析方式,是动态还是非动态
1
2
3
4
5
6
7
8
9
10
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
SqlSource sqlSource = null;
if (this.isDynamic) {//判断是否为动态sql,是则调用
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}
*8.打开RawSqlSource方法
1
2
3
4
5
6
7
8
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}//getSql(configuration, rootSqlNode)从名称可以得知,从配置对象,和读取的结点信息获取String类型的sql对象
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
}

调用的是第一个构造方法,在该构造方法中又调用第二个构造方法,来创建RawSqlSource对象,在第二个构造方法中有sqlSourceParser.parse方法,很接近答案了
*9.跟进sqlSourceParser.parse方法中

1
2
3
4
5
6
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
}

可以看到mybaits在这里把#{,}切开来进行了替换,看最后的返回值为 return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings()); 根据名称可以知道一个是配置对象,一个是替换后的sql语句,一个是原先在#{}中的参数信息的集合,可以推出ParameterMappingTokenHandler对象应该是用于参数的存放
第一个问题差不多就算是明白了,下一个问题

2.第二个问题

  1. 回到MapperAnnotationBuilder的parse方法,当没有加载到对应的xml文件时,继续向下执行,在try,catch中,有一个if判断,如果读取了xml文件不会进入,未读,则调用parseStatement方法中去类中读取注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public void parse() {
    String resource = this.type.toString();
    if (!this.configuration.isResourceLoaded(resource)) {//已加载过则不执行
    this.loadXmlResource();//加载寻找package对应的xml文件,有的话,则加载。没有则执行完向下
    this.configuration.addLoadedResource(resource);//里面是Set,表示已加载的resource
    this.assistant.setCurrentNamespace(this.type.getName());
    this.parseCache();//解析缓存
    this.parseCacheRef();
    Method[] methods = this.type.getMethods();
    Method[] var3 = methods;
    int var4 = methods.length;

    for(int var5 = 0; var5 < var4; ++var5) {
    Method method = var3[var5];
    try {
    if (!method.isBridge()) {
    this.parseStatement(method);//如果读取了xml文件不会进入,未读,则在该方法中去类中读取注解
    }
    } catch (IncompleteElementException var8) {
    this.configuration.addIncompleteMethod(new MethodResolver(this, method));
    }
    }
    }
    this.parsePendingMethods();
    }
  2. 进入parseStatement方法

    1
    2
    3
    4
    5
    void parseStatement(Method method) {
    Class<?> parameterTypeClass = this.getParameterType(method);
    LanguageDriver languageDriver = this.getLanguageDriver(method);
    SqlSource sqlSource = this.getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {//没截全

仔细看有一个getSqlSourceFromAnnotations方法,获取SqlSource对象从注解中
3.进入getSqlSourceFromAnnotations方法,可以看到开始获得注解信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
Class<? extends Annotation> sqlAnnotationType = this.getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = this.getSqlProviderAnnotationType(method);
Annotation sqlProviderAnnotation;
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
} else {
sqlProviderAnnotation = method.getAnnotation(sqlAnnotationType);
String[] strings = (String[])((String[])sqlProviderAnnotation.getClass().getMethod("value").invoke(sqlProviderAnnotation));
return this.buildSqlSourceFromStrings(strings, parameterType, languageDriver);
}
} else if (sqlProviderAnnotationType != null) {
sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(this.assistant.getConfiguration(), sqlProviderAnnotation, this.type, method);
} else {
return null;
}
} catch (Exception var8) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + var8, var8);
}
}

搞定收工!

0%